Skip to content

fix(egfx): keep the bitmap cache across ResetGraphics - #2011

Open
AKolenda wants to merge 3 commits into
Devolutions:masterfrom
AKolenda:fix/egfx-keep-cache-on-reset
Open

AKolenda wants to merge 3 commits into
Devolutions:masterfrom
AKolenda:fix/egfx-keep-cache-on-reset

Conversation

@AKolenda

@AKolenda AKolenda commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

The compositor emptied the bitmap cache on every ResetGraphics. Windows reuses cached toolbars, icons and text after a resize, so subsequent CacheToSurface commands could leave those regions black until a fresh upload.

Compositor::reset now drops surfaces and pending output while retaining the bitmap cache and its allocation charge. MS-RDPEGFX 3.3.5.14 resets the graphics output; the bitmap-cache state is described separately in 3.3.1.4. To preserve space for replacement surfaces in the 256 MiB budget, a cache exceeding the protocol's 100 MiB maximum is discarded during reset.

The regression tests verify that cached content remains renderable after reset, surface allocation charges are released, retained cache charges remain accounted for, and an oversized cache is discarded.

Validation

Rechecked this revision: all 29 compositor unit tests pass, and the current regular CI suite is green. The review's specification-reference corrections and allocation-cap safeguard are already included in 46ee89b.

Copilot AI balanced review requested due to automatic review settings September 26, 2026 06:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

This pull request may overlap with #1977.

Both touch EGFX ResetGraphics handling of the bitmap cache: PR 1977 describes correcting which caches a ResetGraphics clears to fix stale pixels, while this change keeps the cache across reset under the MS-RDPEGFX 3.3.1.4 size cap in the same compositor. Shared scope in one area for human assessment.

This notice is advisory only. Automated review continues as usual, and how these pull requests relate is for maintainers and authors to decide.

Note

LLM-assisted content (no human feedback).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR keeps the egfx bitmap cache across ResetGraphics and recomputes allocated_bytes to the surviving cache charge. Independently verified: the recompute is exact (copy_region always yields w*h*4 per charged tile), every other charged pool (surfaces, frame, ready) is cleared with its charge, and cache retention matches MS-RDPEGFX 3.3.5.14, consistent with existing Progressive/ClearCodec handling; tests cover accounting and a post-reset CacheToSurface. No protocol or correctness defects. The single valid candidate (skeptical, reset-retains-cache-budget-charge) is refined: post-reset surface creation competes with retained tiles under the shared 256 MiB budget and refusal is a silent no-op; the refinement corrects the claim that the trade-off is unacknowledged (the PR body mentions the retained budget share) and the recovery wording. Protocol and code-compressor reported no findings.

Comment thread crates/ironrdp-egfx/src/compositor.rs Outdated
@github-actions github-actions Bot added the ai-reviewed/1 One automated review completed label Sep 26, 2026
@AKolenda
AKolenda deployed to llm-providers September 26, 2026 18:50 — with GitHub Actions Active
@AKolenda

Copy link
Copy Markdown
Contributor Author

On the overlap notice: #1977 contains the same Compositor::reset change (keep the cache and recompute the charge from it) as one part of a larger ResetGraphics fix, and the two conflict in compositor.rs. This PR is only that change, in case it helps to land it separately. If #1977 merges first, this one can be closed.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR keeps the EGFX bitmap cache (and its byte charge) across ResetGraphics in Compositor::reset, matching MS-RDPEGFX 3.3.5.14 (reset only resizes the output buffer) and the crate's existing treatment of Progressive/ClearCodec contexts across resets. The accounting recomputation is correct: the surface, frame and ready pools are cleared along with their vectors, so only the cache charge remains, and the test verifies the kept tile still produces output via CacheToSurface. Both specialist findings are valid and published: a doc-only mis-citation of the cache messages as 2.2.2.10/2.2.2.11 (low), and a medium resilience regression where a hostile peer can pin the shared 256 MiB budget in u16-keyed cache slots and now stays pinned across resets, permanently starving post-reset surface creation and output materialization. code-compressor reported no findings.

Comment thread crates/ironrdp-egfx/src/compositor.rs Outdated
Comment thread crates/ironrdp-egfx/src/compositor.rs Outdated
@github-actions github-actions Bot added ai-reviewed/2 Final automated review completed and removed ai-reviewed/1 One automated review completed labels Sep 26, 2026
Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Sep 28, 2026
…#2007)

Windows lists every dynamic channel it intends to move in its Soft-Sync
request, including the ones the client declined with NO_LISTENER.
Against a Windows 11 host the request lists channels 2, 6, 7, 8, 9, 10,
11 and 12 (CoreInput, MouseCursor, Graphics, Video, Geometry, ...), and
only channel 7, the graphics pipeline, is open.

`process_soft_sync_request` dropped a whole channel list as soon as one
ID in it was not open. The tunnel was then never switched, and the
channels the client had opened stayed on TCP while the server was
already sending them on the tunnel (MS-RDPEDYC 3.2.5.3.1).

Unopened channels are now skipped one by one, and the tunnel is switched
for the rest.

## Testing

- New `dvc::client::soft_sync_skips_channels_the_client_did_not_open` in
`ironrdp-testsuite-core`.
- Live, against a Windows 11 host over RDP-UDP version 2, with the
viewer built from a branch that also carries the tunnel and client PRs
of this series: the Soft-Sync request above now switches the tunnel, and
the graphics pipeline moves onto it.

## Checks

- `cargo fmt --all -- --check`
- `cargo clippy --workspace --all-targets --features helper,__bench
--locked -- -D warnings`
- `cargo test --locked -p ironrdp-testsuite-core -p
ironrdp-testsuite-extra`, plus the lib tests of the crates touched here
- `cargo test --workspace --locked` on a branch that merges this PR with
the other Windows interop PRs from this series
- `typos` on the changed files

## Series

These PRs port the Windows interop fixes and Linux backends from a
downstream IronRDP fork, so the fork can be retired. Each one is based
on `master` and can be reviewed and merged on its own. I also checked
that all of them merge cleanly together in this order.

- #2007 fix(dvc): Soft-Sync tunnel with declined channels
- #2008 fix(session)!: channels and graphics on the tunnel
- #2009 fix(rdpeudp): auto-detect on the tunnel
- #2010 fix(graphics)!: SRL streams from Windows
- #2011 fix(egfx): bitmap cache across ResetGraphics
- #2012 feat(session): bandwidth measurements during the session
- #2013 feat(client): graphics pipeline and RDP-UDP version options
- #2014 fix(client): resize reconnects on the graphics pipeline
- #2015 feat(client): transport event
- #2016 feat(cliprdr): Linux clipboard backend
- #2017 feat(rdpdr): printer on Linux and macOS

Co-authored-by: AKolenda <testedemail2222@gmail.com>
AKolenda added 2 commits September 27, 2026 23:17
The compositor emptied the bitmap cache on every ResetGraphics. Windows sends
a ResetGraphics for every desktop resize and afterwards keeps pasting
toolbars, icons and text with CacheToSurface from slots it filled before the
reset, so those regions stayed black after each resize until something
forced a fresh upload.

The bitmap cache is not part of the graphics output: MS-RDPEGFX 3.3.5.14
only resizes the Graphics Output Buffer, and slots are released by
EvictCacheEntry, a cache import or the end of the channel. This is the same
reasoning the client already applies to the Progressive and ClearCodec
contexts. The reset now keeps the cache and its share of the allocation
budget, and still drops surfaces and pending output.
MS-RDPEGFX 3.3.1.4 caps the bitmap cache at 100 MB, or 16 MB with
SMALL_CACHE, so the charge kept across ResetGraphics leaves a conforming
server room in the budget for the surfaces it creates after the reset.
MS-RDPEGFX 3.3.1.4 caps the bitmap cache at 100 MB. A server that filled
the cache past that cap kept it across ResetGraphics, together with its
share of the compositor budget, and could leave no room for the
surfaces it creates after the reset. Such a cache is now dropped with
the surfaces, and a cache within the cap is kept as before.

Also cite the bitmap cache as MS-RDPEGFX 3.3.1.4 and SurfaceToCache as
2.2.2.6. The comments cited 2.2.2.10 and 2.2.2.11, which are DeleteSurface
and StartFrame.
@AKolenda
AKolenda force-pushed the fix/egfx-keep-cache-on-reset branch from fca225a to 46ee89b Compare September 28, 2026 05:18
@AKolenda
AKolenda deployed to llm-providers September 28, 2026 05:19 — with GitHub Actions Active
@github-actions github-actions Bot added needs-review A human reviewer is the current next actor size/S Size: up to 199 counted lines and 5 files; exceeds XS in either measure and removed size/XS Size: up to 49 counted lines and 2 files labels Sep 28, 2026
@AKolenda

AKolenda commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Rechecked all three review findings against 46ee89b. The cache citations are corrected, caches over 100 MiB are dropped on reset, and retained cache charges leave at least 156 MiB of the shared 256 MiB budget for surfaces. The regression reset_drops_a_cache_over_the_protocol_cap verifies that an overfilled cache cannot starve post-reset surfaces.

Reran all 29 compositor unit tests; all passed. Current CI also passes. No further code changes were needed, and the three outdated review threads are now addressed.

This branch was successfully deployed

1 active deployment
llm-providers — 46ee89b5 Deployed Sep 28, 2026 by AKolenda via Classify pull request #836
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed/2 Final automated review completed kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/medium Behavioral change that does not substantially alter a core public API scope/core Touches the core architectural tier size/S Size: up to 199 counted lines and 5 files; exceeds XS in either measure triage/overlap Possible overlap with another pull request; advisory only

Development

Successfully merging this pull request may close these issues.

2 participants